1つのプロジェクト内でCoreCLRのビルドとUnityのビルドを共存させる
概要
あまり完璧ではない形だが実現できた。
やりたいことは、
・Unityでクライアントを書く
・CoreCLRでサーバとかを書く
・双方に跨る形で、データとかロジックのコードを共有する
・Unityのコンパイルに合わせてCoreCLR側もコンパイルする
・Unityの実行にあわせてサーバ起動する
とかそんな感じ。
Unityの使っている.Net 3.5っていう古代の生物と、CoreCLRだと、それなりに差異の数がある。
環境による差分
.Net 3.5のころに存在するいろんな機能が「よい実装方法や手法ではなかった」という理由でCoreCLR側でガンガン滅ぼされている。
詳しくはだいたいこっちに書いた。
CoreCLRで遭遇する楽しいエラーというか滅ぼされたものとかと戦う
http://sassembla.github.io/Public/2016:07:09%2014-04-11/2016:07:09%2014-04-11.html
で、今回実現できたフォルダ構成
こんな構成。Unityと外部とでコードを共有する仕掛けになっている。
UnityProjectFolder/
Assets/
いろいろクライアント専用コードを置くことができる。当たり前。
XrossPeer/
このへんに、クライアントとサーバで共有されているコードを置くことができる。
拡張子は自動的に~.client.cs になる。
XrossPeer_Peered/
このPeer=クライアントでだけ改変が必要なコードを置く場所。
XrossPeer_Util/
XrossPeerUtilility.cs
ログを吐いたりするための、全環境共通のロガー。
どのPeerでも使えるようになってないといけない、あんまり頑張らないロガー。
Editor/
この機構を動かすためのコードを置く場所。XrossPeerの提供者 = 自分が書いたコードしか置かれない前提の場所。
PeerがUnityのAssets以下にあるときにしか評価されないので、Unity以外のPeerへは出力されない。
主にコードの複製とサーバ側のコードのコンパイル、起動、再起動とかをハンドラの形で提供する。
ServerContext/
いろいろサーバPeerで動作するコードを置く場所。Assetsフォルダと同じ階層で、つまりUnityのコンパイルする対象外。
XrossPeer/
このへんに、クライアントとサーバで共有されているコード
拡張子は自動的に~.servercontext.cs とかになる。
XrossPeer_Peered/
このPeer=クライアントでだけ改変が必要なコード
XrossPeer_Util/
XrossPeerUtilility.cs
Assets/XrossPeer/XrossPeer_Utilフォルダ内と同じもの。
この機構のためにXrossPeerというAssetを開発して使っている。
Assets/XrossPeerフォルダにはクライアント側の共有コードが置かれ、XrossPeer_Peeredフォルダには「クライアントとサーバで異なるライブラリを使ったりする場合の差異のあるコード」を置く想定。
他に、全Peerで共通のUtilとしてログ機能とかを提供する。共通で出て欲しいログとかを書くとしたらこの辺になりそう。環境ごとのログ実装の差し替えも可能。
XrossPeer_Peeredについて
何を置くのを想定してるかというと、
具体的には、例えばJSONのエンコード/デコードについて、
・クライアントではUnityのJsonUtilityを使う
・サーバではJson.netを使う
などの場合に、共通のインターフェースを使うコードは各XrossPeer_Peeredフォルダ直下に置いておいて、
XrossPeer_Peered直下にはそれぞれのライブラリを使用するコード(型や引数などのインターフェースは共通で、実装のみを変えたもの)を用意する。
このXrossPeer_Peeredフォルダの内容は自動的にはコピーされないので、局所的なライブラリとか、
.Net3.5 ~ CoreCLR間で互換性のないメソッドとか記法を各Peered内に隠蔽することができる。
こいつを開発してる動機はまさにこのPeeredフォルダにあって、今後Unityのバージョンが上がろうが、動作するプラットフォームとしてのクライアント/サーバの差は絶対に出るし、
例えばJsonUtilityが良い例なんだけど、C++実装されてるアレらを超えるクライアント側ライブラリって中々無いわけで。
それを綺麗に分ける方法をずっと探していて、これを作った、という感じ。
コードのミラーリング機構あり
単に、クライアント -> サーバ、 サーバ -> クライアントへと、Peeredフォルダは除外した上でXrossPeerフォルダの中のコードを上書きする、という機構。
ただし、これだとどっちのコードをいじっているのかわからなくなるので、
差分検出機とコードの末尾に強制的に.client.csとか.server.csって付くように調整してある。
XrossPeer内に置いた全ての.csコードの拡張子は、そのPeerの属する名前に拡張子が改造される。
この機能はUnityのエディタ機能拡張として実装した。
Peerの抽象化
ここまで散々Peer = クライアント/サーバの二軸みたいに書いていたが、別に「UnityのAssets以下にあるものと、その他のもの」くらいの最大的な区別しかなく、
ざっくり言うと
UnityのAssetsの中にあるPeer A と、フォルダBに展開されているPeer B、フォルダCに展開されているPeer C、、 みたいな拡張も可能にしている。
まあ普通複数のPeerを同時に動かすことは無いと思うんだけど、例えばバーチャルクライアントみたいなものをでっち上げたい時に役に立つ。
上の例だとA, B, Cっていう合計3つのPeerがあるが、このうちAを実際にプレイするクライアント、Bを僚機、Cをサーバ、みたいに振り分けることも可能なようにしてある。
サーバの起動コントロール
自分が作っているものの都合上、次のフックポイントがあると便利だった。
・特定のPeerのコードのコンパイルを行う(コントロールできるのはUnity外のみ、ようはCoreCLR系。
・特定のPeerのコンパイルの成否をどっかに吐く(単に書き出すだけになると思うが
・特定のPeerのプロセスを起動
・特定のPeerのプロセスを再起動
このへんはAssetStoreに出して通った健全?な手法があるんで俺そういうのやっててよかったなあと思った。
依存してるもの
CoreCLR環境が整ってないと、CoreCLR側がコンパイルできない。
MacだろうとWinだろうと一瞬で環境が用意できる。だいたい3分もあればマジでできる。
参考:
「.NET Coreとツール類の今」
https://docs.com/bonprosoft/4626/net-core
モロにCoreCLRのexeに依存しちゃってるので、その辺を整理整頓する方法を考え中。
同梱するわけにはいかねえべえ、、、
コード
ここ。 そのうちアップされる。
XrossPeer
https://github.com/sassembla/XrossPeer
試験中の奴はRolePlayingChatっていうのに含まれている。
RolePlayingChat
https://github.com/sassembla/RolePlayingChat